/**
 * 
 */
package mdp.solve;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;

import RC.structs.DTree;
import RC.structs.DTreeNode;

import util.Pair;

import logic.add.DD;
import mdp.id.AADDHandler;
import mdp.id.ADDHandler;
import mdp.id.LogicRDDL2DD;
import mdp.id.LogicRDDL2DD.DEBUG;
import mdp.id.LogicRDDL2DD.ORDER;

/**
 * @author NR
 *
 * TODO
 */
public class MaxDecompositionAADD<T extends LogicRDDL2DD>  implements Runnable{
	
	private static double	EPSILON	= 0;
	private T _handler;
	private ArrayBlockingQueue<Pair<T, Integer>> _bq = null;
	
	private static final boolean	MAX_ON	= true;
	/**
	 * 
	 */
	private ArrayList<String>	_maxOrder;
	private ArrayList<String>	_sumOrder;
	
	public MaxDecompositionAADD(T handler, String domain, String instance, double epsilon,
			ArrayBlockingQueue<Pair<T, Integer>> bq) {
		_bq = bq; 
		EPSILON = epsilon;
		_handler = handler;
		_handler.buildFromFile(domain, instance);
		_sumOrder = handler.getSumOrderString();
		_maxOrder = handler.getMaxOrderString();
		
//		_dtree = new DTree<T>(handler, new HashSet<String>(_sumOrder), _maxOrder );
	}

	/* (non-Javadoc)
	 * @see java.lang.Runnable#run()
	 */
//	@Override
	public void run() {
		
		if( _handler._bRewardActionDependent ) {
			System.err.println("this procedure only applicable for action independent rewards");
			System.exit(1);
		}
		
		int _valueDD = _handler.DD_ZERO;
//		int _policy = _handler.DD_ZERO;
		int iter = 0;
		boolean done = false;
		long timetosolve = 0;
		
		while( !done ) {
			long itertime = System.currentTimeMillis();
			int Vprev = _valueDD;
			int Vprimed = _handler.swapForPrimes(_valueDD);
			_handler.saveThis(Vprev);
			Pair<Integer, Integer> vq = maxRegress(Vprimed, iter);
			
			_valueDD = vq._o1;
			
			int diff = _handler._context.applyInt(_valueDD, Vprev, DD.ARITH_MINUS);
			
			double error = Math.max(
					Math.abs(_handler.getMax(diff)), 
					Math.abs(_handler.getMin(diff) == _handler.getNegInf() ? 0 : _handler.getMin(diff)));
			++iter;
			
			if( error < EPSILON || _handler.isHorizon(iter+1) ) {
				done = true;
//				_handler.saveThis(vq._o2);
//				_policy = vq._o2;
			}
			
			itertime = System.currentTimeMillis() - itertime;
			timetosolve += itertime;
			
			System.out.println("Iteration " + iter + " " + error);
			_handler.forgetThis(Vprev);
			_handler.flushCaches(false, _valueDD);
		}
		System.out.println(timetosolve);
		
		int Vprimed = _handler.swapForPrimes(_valueDD);
		Pair<Integer, Integer> vq = _handler.regress(Vprimed, null, true, true);
		
//		int _policyDD = _handler.makePolicy(vq._o2);
		int _policy = _handler.makePolicy(vq._o2);
		
		_handler.setValueDD(_valueDD);
		_handler.setPolicyDD(_policy);
		
		ArrayList stats = _handler.playDD(_policy);
		stats.add(timetosolve/(1000*60d));
		System.out.println(stats);
//		_handler.showGraph(_policyDD);
		
		if( _bq != null ) {
			try
			{
				_bq.put(new Pair<T, Integer>(_handler, _valueDD));
//				_bq.put(new Pair<T, Integer>(_handler, _policy));
			} catch (InterruptedException e)
			{
				e.printStackTrace();
			}
		
		}
	}
	
	/**
	 * @param pVprimed
	 * @param pObject
	 * @return
	 */
	private Pair<Integer, Integer> maxRegress(int pVprimed, int iter) {
		int vp = _handler.constrainThis(pVprimed, null);
		
		//regress exogenous
		Pair<Integer, Pair<Integer, Integer>> ret = regressExogenous(vp);
		vp = ret._o1;
		int sumInd = ret._o2._o1;
		int maxInd = ret._o2._o2;
		
		//regress control vars
		Pair<Integer, Integer> vx = regressControl(vp, sumInd, maxInd, new HashMap<Integer, Boolean>(),
					_handler.isHorizon(iter+1));
		
		return vx;
		
	}

	/**
	 * @param pVp
	 * @param pSumInd
	 * @param pMaxInd
	 * @param pHashMap
	 * @param pRoot
	 * @param pHorizon
	 * @return
	 */
	private Pair<Integer, Integer> regressControl(int pVp, int pSumInd,
			int pMaxInd, HashMap<Integer, Boolean> ass,
			boolean makePolicy) {

		//for each x'
		//	if size > BIGADD
		//		ask curnode for cutset
		//		for each initialization to cutset
		//			add to ass
		//			call regCon with dd
		//			max with cur val fn.
		//			update policy if need be
		//			remove from ass
		//			return
		//	else 
		//		continue regression
		//discount and add reward
		//store X
		//max out actions vars
		//make policy
		//return v,q
		
		int Vcur = pVp;
		int policy = _handler.DD_ONE;
		
		int maxInd = pMaxInd;
		
		ArrayList<Pair<Integer, String>> scores = _handler.getScores(ass, 0);
		
		for( int i = pSumInd; i < _sumOrder.size(); ++i ) {

				_handler.saveThis(policy);
				
				String var = _sumOrder.get(i);
				int q = _handler.computeExpectation(Vcur, _handler.toID(var), ass);

				updateScores(scores, i);
				Vcur = maxOutZeros(q, scores);
				
//				if( makePolicy ) {
//					int qfixed = _handler.fix(_handler._context.scalarAdd(q, 1.0d));
//					int vfixed = _handler.fix(Vcur);
//					
//					int diff = _handler._context.applyInt(vfixed, qfixed, DD.ARITH_MINUS);
//					int actzero = _handler.thresholdAt(diff, -1.0d);
//					int actone = _handler._context.applyInt(_handler.DD_ONE, actzero, DD.ARITH_MINUS);
//					
//					_handler.forgetThis(policy);
//					
//					policy = _handler._context.applyInt(policy,	
//						actone,
//						DD.ARITH_PROD);
					
//				}
				
				_handler.flushCaches(false, Vcur, policy);
				
		}
		
		Vcur = _handler.DiscountAndAddReward(Vcur, ass);
		Vcur = _handler.maxOutActionVars(Vcur);
		Vcur = _handler.fix(Vcur);
		
		return new Pair<Integer, Integer>(Vcur, policy);
	}

	
	/**
	 * @param pQ
	 * @param pScores
	 * @return
	 */
	private int maxOutZeros(int pQ, ArrayList<Pair<Integer, String>> pScores) {
		int ret = pQ;
		ArrayList<Pair<Integer, String>> dels = new ArrayList<Pair<Integer,String>>();
		
		for( Pair<Integer, String> p : pScores ) {
			if( p._o1 == 0 ) {
				String act = p._o2;
				System.out.println("Maxing : " + act  );
				
				int id = _handler.toContextID(act);
				ret = _handler._context.opOut(ret, id, DD.ARITH_MAX);
				dels.add(p);
			}
		}
		
		for( Pair<Integer, String> p : dels ) {
			pScores.remove(p);
		}
		
		return ret;
		
	}

	/**
	 * @param pScores
	 * @param pI
	 * @return
	 */
	private void updateScores(
			ArrayList<Pair<Integer, String>> pScores, int pI) {
		String xp = _sumOrder.get(pI);
		ArrayList<String> acts = (ArrayList<String>) _handler._hmvars2act.get(xp);
		for( Pair<Integer, String> p : pScores ) {
			if( acts.contains(p._o2) ) {
				--p._o1;
			}
		}
	}

	/**
	 * @param pCutSet
	 * @return
	 */
	private ArrayList<String> retainActionsOnly(Set<String> pCutSet) {
		Set<String> copy = new HashSet<String>(pCutSet);
		copy.retainAll(_handler._alactionvars);
		return new ArrayList<String>(copy);
	}

	/**
	 * @return
	 */
	

	/**
	 * @param pVp
	 * @return
	 */
	private Pair<Integer, Pair<Integer, Integer>> regressExogenous(int pVp) {
		int ret = pVp;
		int i ;
		
		for( i = 0 ; i < _sumOrder.size(); ++i ) {
			String cptvar = _sumOrder.get(i);
			int id = _handler.toID(cptvar);
			int cpt = _handler.getDualDiagram(id, null);
			if( _handler.isExogenous(cpt) ) {
				ret = _handler.computeExpectation(ret, id, null);
				_handler.flushCaches(false, ret);
			}else {
				break;
			}
		}
		return new Pair<Integer, Pair<Integer,Integer>>(ret, new Pair<Integer, Integer>(i, 0));
	}

	public static void main(String[] args) throws InterruptedException {
		AADDHandler handle = new AADDHandler(DEBUG.PROBLEM_INFO , ORDER.INTERLEAVE);
		Runnable vic = 
			new MaxDecompositionAADD<AADDHandler>(handle, args[0], args[1], 
					Double.parseDouble(args[2]), null);
		Thread vicWorker = new Thread(vic, "VIC");
		vicWorker.start();
		vicWorker.join();
	}
	
}
